package com.pai4j.pgc.service.community;
import com.google.common.base.Joiner;
import com.pai4j.common.bean.PAIPageResponseBeanUtil;
import com.pai4j.common.constants.MessageConstants;
import com.pai4j.common.constants.WebConstant;
import com.pai4j.common.enums.community.*;
import com.pai4j.common.service.FreeMarkEngineService;
import com.pai4j.common.util.*;
import com.pai4j.domain.community.vo.request.CCAnswerSaveRequestVO;
import com.pai4j.domain.community.vo.request.CCQuestionSaveRequestVO;
import com.pai4j.domain.community.vo.response.cc.*;
import com.pai4j.domain.community.vo.response.message.MessageResourceVO;
import com.pai4j.domain.vo.dto.SystemMessageDTO;
import com.pai4j.domain.vo.llm.ChatCompletionMessage;
import com.pai4j.domain.vo.llm.ChatCompletionResponse;
import com.pai4j.domain.vo.llm.ChatMessageRole;
import com.pai4j.domain.vo.response.UserBaseResponseInfoVO;
import com.pai4j.pgc.dao.community.ICCAnswerDAO;
import com.pai4j.pgc.dao.community.ICCQuestionDAO;
import com.pai4j.pgc.entity.community.CCQuestionEntity;
import com.pai4j.pgc.pojo.CTCHomeRecommendQuestionVO;
import com.pai4j.pgc.service.aig.DeepSeekAIService;
import com.pai4j.pgc.service.behavior.AbstractResourceBehaviorService;
import com.pai4j.pgc.service.behavior.ResourceBehaviorFactory;
import com.pai4j.pgc.service.event.CourseStudyEvent;
import com.pai4j.pgc.service.message.builder.SystemMessageBuilder;
import com.pai4j.pgc.service.queue.UserMessageQueue;
import com.pai4j.remote.user.UserServiceClient;
import jakarta.persistence.criteria.CriteriaBuilder;
import jakarta.persistence.criteria.CriteriaQuery;
import jakarta.persistence.criteria.Predicate;
import jakarta.persistence.criteria.Root;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.data.redis.core.DefaultTypedTuple;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;
import java.util.*;
import java.util.stream.Collectors;

/**
 *  技术社区问答
 *
 * @author: CYM-pai
 * @date: 2025/10/26 21:36
 **/
@Slf4j
@Service
public class CCQuestionService implements ResourceMessageInterface {

    @Autowired
    private RedisUtil redisUtil;
    @Autowired
    private CCQuestionService questionService;
    @Autowired
    private ICCQuestionDAO questionDAO;
    @Autowired
    private ICCAnswerDAO answerDAO;
    @Autowired
    private UserServiceClient userServiceClient;
    @Autowired
    private CCPathService pathService;
    @Autowired
    private UserMessageQueue userMessageQueue;
    @Autowired
    private CTCScheduleService ctcScheduleService;
    @Autowired
    private FreeMarkEngineService freeMarkEngineService;
    @Autowired
    private DeepSeekAIService deepSeekAIService;
    @Autowired
    private CCAnswerService answerService;
    @Autowired
    private CTCScheduleService scheduleService;
    @Autowired
    private ApplicationContext applicationContext;

    private static final String AI_ROBOT_USER_ID = "CU_dasidh9823e90234032jdo23807urod";

    /**
     * 发布/编辑问题
     * @param questionSaveRequest
     * @param userId
     * @return
     */
    public Long save(CCQuestionSaveRequestVO questionSaveRequest, String userId) {
        CCQuestionEntity question = this.convertEntity(questionSaveRequest, userId);
        question = questionDAO.save(question);
        CCQuestionEntity finalQuestion = question;
        ThreadPoolExecutorUtil.execute(() -> {
            // 刷新最新问题列表缓存
            addCCNewQuestionCache(finalQuestion.getPathId(), finalQuestion.getId(), finalQuestion.getCreateDate().getTime());
            // 刷新问题详情缓存
            refreshQuestionDetailCache(finalQuestion);
            if (questionSaveRequest.getId() == null) {
                // 首次提交问题，ai回复
//                aiAutoComment(finalQuestion);
            }
        });
        return question.getId();
    }

    private void aiAutoComment(CCQuestionEntity qa) {
        String title = qa.getTitle();
        String description = qa.getDescription();
        boolean isAuto = !AI_ROBOT_USER_ID.equals(qa.getAuthor());
        if (!isAuto) {
            return;
        }
        CTCPathDetailVO star = pathService.getPathBaseFromCache(qa.getPathId());
        UserBaseResponseInfoVO user = userServiceClient.getUserBaseInfoByUserId(qa.getAuthor());
        Map<String, Object> vars = new HashMap<>();
        vars.put("starName", star.getName());
        vars.put("userName", user.getUserName());
        vars.put("title", title);
        vars.put("description", description == null ? "" : description);
        String prompt = null;
        try {
            prompt = freeMarkEngineService.getContentByTemplate(PromptTemplateEnum.STAR_KEFU.getName(), vars);
            final List<ChatCompletionMessage> messageList = Arrays.asList(
                    new ChatCompletionMessage(ChatMessageRole.USER.value(), prompt));
            ChatCompletionResponse chatCompletionResponse = deepSeekAIService.chat(messageList);
            String reply = AiResponseUtil.getJsonString(chatCompletionResponse.getChoices().get(0).getMessage().getContent());
            if (StringUtils.isBlank(reply)) {
                return;
            }
            CCAnswerSaveRequestVO answerSaveRequest = new CCAnswerSaveRequestVO();
            answerSaveRequest.setQuestionId(qa.getId());
            answerSaveRequest.setContent("<div style='padding-top: 12px'>" + reply.replaceAll("\n", "<br/>") + "</div>");
            answerService.save(answerSaveRequest, AI_ROBOT_USER_ID);
        } catch (Exception e) {
            log.error("===>aiAutoComment error.", e);
        }
    }

    public boolean delete(Long questionId, String userId) {
        CCQuestionEntity qa = questionDAO.findById(questionId).orElse(null);
        Assert.isTrue(qa != null, "问题不存在！");
        Assert.isTrue(!qa.getStatus().equals(QuestionStatusEnum.DELETED.getStatus()), "问题已删除！");
        boolean hasDeleteAuth = userServiceClient.isAdmin(userId) || qa.getAuthor().equals(userId);
        Assert.isTrue(hasDeleteAuth, "无删除权限！");
        qa.setStatus(QuestionStatusEnum.DELETED.getStatus());
        qa.setUpdateBy(userId);
        qa.setUpdateDate(new Date());
        questionDAO.save(qa);
        // 刷新缓存
        ThreadPoolExecutorUtil.execute(() -> this.deleteQuestionByCache(qa.getPathId(), questionId));
        return true;
    }

    private void deleteQuestionByCache(Long pathId, Long id) {
        this.refreshQuestionDetailCache(id);
        ctcScheduleService.refreshCTCHomeQuestionList(pathId);
        initCTCNewQuestionCache(pathId);
        initCTCDoneQuestionCache(pathId);
    }

    /**
     * 设置经验置顶
     * @param id
     * @param status
     * @param userId
     */
    public void setTop(Long id, Boolean status, String userId) {
        CCQuestionEntity questionEntity = questionDAO.findById(id).orElse(null);
        Assert.isTrue(questionEntity != null, "操作失败:问题不存在!");
        questionEntity.setTop(status);
        questionEntity.setTopDate(status ? new Date() : null);
        questionEntity.setUpdateBy(userId);
        questionEntity.setUpdateDate(new Date());
        questionEntity = questionDAO.save(questionEntity);
        /**
         * 刷新置顶经验缓存
         */
        this.refreshQuestionDetailCache(questionEntity);
        this.setQuestionTopListCache(questionEntity.getPathId(), id, status);
        if (ObjectUtils.equals(status, true)) {
            // 加精，积分奖励
            SystemMessageBuilder systemMessageBuilder = new SystemMessageBuilder();
            SystemMessageDTO systemMessage = systemMessageBuilder.buildResourceMessage(
                    MessageConstants.TypeEnum.TOP,
                    userId,
                    "您在CTC中文技术社区发布的问题《"+questionEntity.getTitle()+"》被置顶，感谢您的参与，积分奖励已到账！",
                    ResourceTypeEnum.CTC_QUESTION,
                    String.valueOf(id));
            userMessageQueue.submit(systemMessage);
        }
    }

    /**
     * 设置经验推荐
     * @param id
     * @param status
     * @param userId
     */
    public void setRecommend(Long id, Boolean status, String userId) {
        CCQuestionEntity questionEntity = questionDAO.findById(id).orElse(null);
        Assert.isTrue(questionEntity != null, "操作失败:问题不存在!");
        questionEntity.setRecommend(status);
        questionEntity.setRecommendDate(status ? new Date() : null);
        questionEntity.setUpdateBy(userId);
        questionEntity.setUpdateDate(new Date());
        questionEntity = questionDAO.save(questionEntity);
        /**
         * 刷新置顶经验缓存
         */
        this.refreshQuestionDetailCache(questionEntity);
        this.setQuestionRecommendListCache(questionEntity.getPathId(), id, status);
        if (ObjectUtils.equals(status, true)) {
            // 加精，积分奖励
            SystemMessageBuilder systemMessageBuilder = new SystemMessageBuilder();
            SystemMessageDTO systemMessage = systemMessageBuilder.buildResourceMessage(
                    MessageConstants.TypeEnum.RECOMMEND,
                    userId,
                    "您在CTC中文技术社区发布的问题《"+questionEntity.getTitle()+"》被推荐精华，感谢您的参与，积分奖励已到账！",
                    ResourceTypeEnum.CTC_QUESTION,
                    String.valueOf(id));
            userMessageQueue.submit(systemMessage);
        }
    }

    public void updateFree(Long id, String userId) {
        CCQuestionEntity question = questionDAO.findById(id).orElse(null);
        Assert.isTrue(question != null, "状态更新失败，资源不存在");
        boolean free = question.getFree();
        question.setFree(!free);
        question.setUpdateBy(userId);
        question.setUpdateDate(new Date());
        questionDAO.save(question);
        ThreadPoolExecutorUtil.execute(() -> {
            refreshQuestionDetailCache(question);
        });
    }

    /**
     * 设置置顶经验缓存
     * @param pathId
     * @param id
     * @param status
     */
    private void setQuestionTopListCache(Long pathId, Long id, Boolean status) {
        String key = CTCRedisKeyEnum.CTC_TOP_QUESTION_ZSET_CACHE.getKey(pathId.toString());
        if (status) {
            redisUtil.zadd(key, id.toString(), DateUtils.getCurrentTimeMillis(), CTCRedisKeyEnum.CTC_TOP_QUESTION_ZSET_CACHE.getExpireTime());
        } else {
            redisUtil.zremove(key, id.toString());
        }
    }

    /**
     * 设置推荐经验缓存
     * @param pathId
     * @param id
     * @param status
     */
    private void setQuestionRecommendListCache(Long pathId, Long id, Boolean status) {
        String key = CTCRedisKeyEnum.CTC_RECOMMEND_QUESTION_ZSET_CACHE.getKey(pathId.toString());
        if (status) {
            redisUtil.zadd(key, id.toString(), DateUtils.getCurrentTimeMillis(), CTCRedisKeyEnum.CTC_RECOMMEND_QUESTION_ZSET_CACHE.getExpireTime());
        } else {
            redisUtil.zremove(key, id.toString());
        }
    }


    public CTCQuestionDetailVO getPermissionQuestionDetailFromCache(Long questionId, String currUserId) {
        CTCQuestionDetailVO questionDetailVO = getQuestionDetailFromCache(questionId, currUserId);
        if (ObjectUtils.equals(questionDetailVO.getQuestion().getFree(), true)) {
            return questionDetailVO;
        }
        pathService.validatePathPermission(currUserId, questionDetailVO.getPath().getId());
        // 发布资源学习事件
        CourseStudyEvent event = new CourseStudyEvent(this, currUserId, ResourceTypeEnum.CTC_QUESTION, questionId);
        applicationContext.publishEvent(event);
        return questionDetailVO;
    }

    /**
     * 查询问题详情
     * @param questionId
     * @param currUserId
     * @return
     */
    public CTCQuestionDetailVO getQuestionDetailFromCache(Long questionId, String currUserId) {
        CTCQuestionDetailVO question = new CTCQuestionDetailVO();
        CTCQuestionBaseVO questionBase = getQuestionBaseFromCache(questionId);
        if (questionBase == null) {
            return question;
        }
        if (StringUtils.isNotBlank(questionBase.getDescription())) {
            questionBase.setDescription(HtmlUtil.xssEscape(questionBase.getDescription()).replaceAll("\n", "<br/>"));
        }
        question.setQuestion(questionBase);
        question.setAuthorInfo(userServiceClient.getUserInfoByUserId(questionBase.getAuthor()));
        question.setPath(pathService.getPathBaseFromCache(questionBase.getPathId(), currUserId));
        question.setViewCount(addQuestionViewCountCacheAndDB(questionId));
        question.setAnswerCount(getQuestionAnswerCountFromCache(questionId));

        AbstractResourceBehaviorService likeService =
                ResourceBehaviorFactory.getBehaviorService(ResourceBehaviorTypeEnum.LIKE.getBehavior());
        Map<Long, Long> likeMap = likeService.getBehaviorCountFromCache(ResourceTypeEnum.CTC_QUESTION.getType(), Arrays.asList(questionId));
        question.setLikeCount(likeMap.getOrDefault(questionId, 0L));
        if (StringUtils.isNotBlank(currUserId)) {
            Map<Long, Boolean> isLikeMap = likeService.getIsBehaviorFromCache(ResourceTypeEnum.CTC_QUESTION.getType(), Arrays.asList(questionId), currUserId);
            question.setIsLike(isLikeMap.getOrDefault(questionId, false));
            AbstractResourceBehaviorService attendService =
                    ResourceBehaviorFactory.getBehaviorService(ResourceBehaviorTypeEnum.COLLECT.getBehavior());
            Map<Long, Boolean> isCollectMap = attendService.getIsBehaviorFromCache(ResourceTypeEnum.CTC_QUESTION.getType(), Arrays.asList(questionId), currUserId);
            question.setIsCollect(isCollectMap.getOrDefault(questionId, false));
        }
        boolean manager = userServiceClient.isAdmin(currUserId) || questionBase.getAuthor().equals(currUserId);
        question.setManager(manager);
        return question;
    }

    /**
     * 管理问题列表查询
     *
     * @param pathId
     * @param kw
     * @param top
     * @param recommend
     * @param pageNo
     * @param pageSize
     * @return
     */
    public PAIPageResponseBeanUtil<CTCQuestionListVO> adminPageList(Long pathId, String kw, Boolean top, Boolean recommend,
                                                                    Integer status, int pageNo, int pageSize) {
        Pageable pageable = PageRequest.of(pageNo - 1, pageSize, Sort.by(Sort.Order.desc("id")));
        Specification<CCQuestionEntity> questionEntitySpecification = buildQuestionQueryCondition(pathId, kw, top, recommend, status);
        Page<CCQuestionEntity> questionEntityPage = questionDAO.findAll(questionEntitySpecification, pageable);
        if (CollectionUtils.isEmpty(questionEntityPage.getContent())) {
            return PAIPageResponseBeanUtil.success(pageNo, pageSize, questionEntityPage.getTotalElements(), Collections.emptyList());
        }
        List<CCQuestionEntity> questionEntities = questionEntityPage.getContent();
        List<Long> qidList = questionEntities.stream().map(CCQuestionEntity::getId).collect(Collectors.toList());
        List<String> authors = questionEntities.stream().map(CCQuestionEntity::getAuthor).collect(Collectors.toList());
        Map<String, UserBaseResponseInfoVO> authorMap = userServiceClient.batchGetUserInfoFromCache(authors);
        Map<Long, Long> viewCountMap = batchGetQuestionViewCount(qidList);
        Map<Long, Long> answerCountMap = batchGetQuestionAnswerCountFromCache(qidList);
        List<CTCQuestionListVO> questionListVOList = questionEntities.stream().map(question -> {
            CTCQuestionListVO questionListVO = new CTCQuestionListVO();
            CTCQuestionBaseVO questionBase = convertCTCQuestionDetailVO(question);
            questionBase.setDescription(null);
            questionListVO.setQuestion(questionBase);
            questionListVO.setAuthorInfo(authorMap.get(question.getAuthor()));
            questionListVO.setPath(pathService.getCTCPathDetailVOFromCache(question.getPathId()));
            questionListVO.setAnswerCount(answerCountMap.getOrDefault(question.getId(), 0L));
            questionListVO.setViewCount(viewCountMap.getOrDefault(question.getId(), 0L));
            return questionListVO;
        }).collect(Collectors.toList());

        return PAIPageResponseBeanUtil.success(pageNo, pageSize, questionEntityPage.getTotalElements(), questionListVOList);
    }

    private Specification<CCQuestionEntity> buildQuestionQueryCondition(Long pathId, String kw, Boolean top,
                                                                        Boolean recommend, Integer status) {
        Specification<CCQuestionEntity> specification = new Specification<CCQuestionEntity>() {
            @Override
            public Predicate toPredicate(Root<CCQuestionEntity> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
                List<Predicate> predicates = new ArrayList<>();
                if (pathId != null) {
                    predicates.add(criteriaBuilder.equal(root.get("pathId").as(Long.class), pathId));
                }
                if (top != null) {
                    predicates.add(criteriaBuilder.equal(root.get("top").as(Boolean.class), top));
                }
                if (status != null) {
                    predicates.add(criteriaBuilder.equal(root.get("status").as(Integer.class), status));
                }
                if (recommend != null) {
                    predicates.add(criteriaBuilder.equal(root.get("recommend").as(Boolean.class), recommend));
                }
                if (StringUtils.isNotBlank(kw)) {
                    predicates.add(
                            criteriaBuilder.or(
                                    criteriaBuilder.like(root.get("title").as(String.class), "%" + kw + "%")));
                }
                Predicate[] pre = new Predicate[predicates.size()];
                criteriaQuery.where(predicates.toArray(pre));
                return criteriaQuery.getRestriction();
            }
        };
        return specification;
    }


    public PAIPageResponseBeanUtil<List<CTCQuestionListVO>> pageList(String typeList, Long pathId, int pageNo, int pageSize) {
        Set<String> questionIdSet = null;
        Long total = 0L;
        if ("new".equals(typeList)) {
            questionIdSet = getNewQuestionListFromCache(pathId, pageNo, pageSize);
            total = getNewQuestionTotalFromCache(pathId);
        } else if ("hot".equals(typeList)) {
            questionIdSet = getHotQuestionListFromCache(pathId, pageNo, pageSize);
            total = getHotQuestionTotalFromCache(pathId);
        } else if ("reward".equals(typeList)) {
            questionIdSet = getRewardQuestionListFromCache(pathId, pageNo, pageSize);
            total = getRewardQuestionTotalFromCache(pathId);
        } else if ("done".equals(typeList)) {
            questionIdSet = getDoneQuestionListFromCache(pathId, pageNo, pageSize);
            total = getDoneQuestionTotalFromCache(pathId);
        }
        List<CTCQuestionListVO> list = new ArrayList<>();
        if (CollectionUtils.isEmpty(questionIdSet)) {
            return PAIPageResponseBeanUtil.success(pageNo, pageSize, total, list);
        }
        List<Long> qidList = questionIdSet.stream().map(Long::valueOf).collect(Collectors.toList());
        /**
         * 批量查询
         */
        List<CTCQuestionBaseVO> questionBaseVOList = this.batchGetQuestionListFromCache(qidList);
        List<String> authors = questionBaseVOList.stream().map(CTCQuestionBaseVO::getAuthor).collect(Collectors.toList());
        Map<Long, Long> viewCountMap = this.batchGetQuestionViewCount(qidList);
        Map<Long, Long> answerCountMap = this.batchGetQuestionAnswerCountFromCache(qidList);
        Map<String, UserBaseResponseInfoVO> authorMap = userServiceClient.batchGetUserInfoFromCache(authors);
        for (CTCQuestionBaseVO questionBase : questionBaseVOList) {
            CTCQuestionListVO ctcQuestionListVO = new CTCQuestionListVO();
            ctcQuestionListVO.setQuestion(questionBase);
            ctcQuestionListVO.setViewCount(viewCountMap.get(questionBase.getId()));
            ctcQuestionListVO.setAnswerCount(answerCountMap.get(questionBase.getId()));
            ctcQuestionListVO.setAuthorInfo(authorMap.get(questionBase.getAuthor()));
            list.add(ctcQuestionListVO);
        }
        return PAIPageResponseBeanUtil.success(pageNo, pageSize, total, list);
    }


    public Set<String> getNewQuestionListFromCache(Long pathId, int pageNo, int pageSize) {
        String key = CTCRedisKeyEnum.CTC_QUESTION_NEW_ZSET_CACHE.getKey(pathId.toString());
        if (!isExistKey(key)) {
            // 初始化社区最新问题缓存
            this.initCTCNewQuestionCache(pathId);
        }
        return redisUtil.zreverseRange(key, (pageNo - 1) * pageSize, pageNo * pageSize);
    }

    /**
     * 查询问题数量
     * @param pathId
     * @return
     */
    public Long getQuestionCountFromCache(Long pathId) {
        String key = CTCRedisKeyEnum.CTC_QUESTION_NEW_ZSET_CACHE.getKey(pathId.toString());
        if (!isExistKey(key)) {
            // 初始化社区最新问题缓存
            this.initCTCNewQuestionCache(pathId);
        }
        return redisUtil.zsize(key);
    }

    public Set<String> getHotQuestionListFromCache(Long pathId, int pageNo, int pageSize) {
        String key = CTCRedisKeyEnum.CTC_QUESTION_HOT_ZSET_CACHE.getKey(pathId.toString());
        return redisUtil.zreverseRange(key, (pageNo - 1) * pageSize, pageNo * pageSize);
    }

    public Set<String> getRewardQuestionListFromCache(Long pathId, int pageNo, int pageSize) {
        String key = CTCRedisKeyEnum.CTC_QUESTION_REWARD_ZSET_CACHE.getKey(pathId.toString());
        return redisUtil.zreverseRange(key, (pageNo - 1) * pageSize, pageNo * pageSize);
    }

    public Set<String> getDoneQuestionListFromCache(Long pathId, int pageNo, int pageSize) {
        String key = CTCRedisKeyEnum.CTC_QUESTION_DONE_ZSET_CACHE.getKey(pathId.toString());
        if (!isExistKey(key)) {
            // 初始化社区最新问题缓存
            this.initCTCDoneQuestionCache(pathId);
        }
        return redisUtil.zreverseRange(key, (pageNo - 1) * pageSize, pageNo * pageSize);
    }

    public Long getNewQuestionTotalFromCache(Long pathId) {
        String key = CTCRedisKeyEnum.CTC_QUESTION_NEW_ZSET_CACHE.getKey(pathId.toString());
        return redisUtil.zSetSize(key);
    }

    public Long getHotQuestionTotalFromCache(Long pathId) {
        String key = CTCRedisKeyEnum.CTC_QUESTION_HOT_ZSET_CACHE.getKey(pathId.toString());
        return redisUtil.zSetSize(key);
    }

    public Long getRewardQuestionTotalFromCache(Long pathId) {
        String key = CTCRedisKeyEnum.CTC_QUESTION_REWARD_ZSET_CACHE.getKey(pathId.toString());
        return redisUtil.zSetSize(key);
    }

    public Long getDoneQuestionTotalFromCache(Long pathId) {
        String key = CTCRedisKeyEnum.CTC_QUESTION_DONE_ZSET_CACHE.getKey(pathId.toString());
        return redisUtil.zSetSize(key);
    }

    /**
     * redis + MYSQL 双写记录浏览量
     * @param questionId
     * @return
     */
    private Long addQuestionViewCountCacheAndDB(Long questionId) {
        String key = CTCRedisKeyEnum.CTC_QUESTION_VIEW_COUNT_CACHE.getKey();
        String field = String.valueOf(questionId);
        Long count = redisUtil.hincrex(key, field);
        if (count == 1) {
            CCQuestionEntity question = questionDAO.findById(questionId).orElse(null);
            count = question.getViewCount() + 1;
            question.setViewCount(count);
            questionDAO.save(question);
            redisUtil.hset(key, field, String.valueOf(count), CTCRedisKeyEnum.CTC_QUESTION_VIEW_COUNT_CACHE.getExpireTime());
        } else {
            ThreadPoolExecutorUtil.execute(() -> questionDAO.increxQuestionViewCount(questionId));
        }
        return count;
    }

    public Long getQuestionAnswerCountFromCache(Long questionId) {
        String key = CTCRedisKeyEnum.CTC_QUESTION_ANSWER_COUNT_CACHE.getKey();
        String count = redisUtil.hget(key, String.valueOf(questionId));
        if (StringUtils.isBlank(count)) {
            Long countFromDB = answerDAO.countByQuestionIdAndDeleted(questionId, false);
            redisUtil.hset(key, countFromDB.toString(), String.valueOf(count), CTCRedisKeyEnum.CTC_QUESTION_ANSWER_COUNT_CACHE.getExpireTime());
            return countFromDB;
        }
        return Long.valueOf(count);
    }

    public Long addQuestionAnswerCountCacheAndDB(Long questionId) {
        String key = CTCRedisKeyEnum.CTC_QUESTION_ANSWER_COUNT_CACHE.getKey();
        String field = String.valueOf(questionId);
        Long count = redisUtil.hincrex(key, field);
        if (count == 1) {
            count = answerDAO.countByQuestionIdAndDeleted(questionId, false);
            redisUtil.hset(key, field, String.valueOf(count), CTCRedisKeyEnum.CTC_QUESTION_ANSWER_COUNT_CACHE.getExpireTime());
        }
        return count;
    }

    /**
     * 批量查询问题回复数
     * @param questionIdList
     * @return
     */
    public Map<Long, Long> batchGetQuestionAnswerCountFromCache(List<Long> questionIdList) {
        if (CollectionUtils.isEmpty(questionIdList)) {
            return Collections.emptyMap();
        }
        String key = CTCRedisKeyEnum.CTC_QUESTION_ANSWER_COUNT_CACHE.getKey();
        List<String> fields = questionIdList.stream().map(String::valueOf).collect(Collectors.toList());
        List<String> caches = redisUtil.hmget(key, fields);
        Map<Long, Long> answerCountMap = new HashMap<>();
        for (int i = 0; i < questionIdList.size(); i++) {
            Long qid = questionIdList.get(i);
            String cache = caches.get(i);
            Long count = 0L;
            if (StringUtils.isNotBlank(cache)) {
                count = Long.valueOf(cache);
            } else {
                count = answerDAO.countByQuestionIdAndDeleted(qid, false);
                count = count == null ? 0L : count;
                redisUtil.hset(key, qid.toString(), String.valueOf(count), CTCRedisKeyEnum.CTC_QUESTION_ANSWER_COUNT_CACHE.getExpireTime());
            }
            answerCountMap.put(qid, count);
        }
        return answerCountMap;
    }


    private Long refreshQuestionViewCountCacheFromDB(Long questionId) {
        String key = CTCRedisKeyEnum.CTC_QUESTION_VIEW_COUNT_CACHE.getKey();
        CCQuestionEntity question = questionDAO.findById(questionId).orElse(null);
        if (question == null) {
            return 0L;
        }
        Long viewCount = question.getViewCount();
        String field = String.valueOf(questionId);
        redisUtil.hset(key, field, String.valueOf(viewCount), CTCRedisKeyEnum.CTC_QUESTION_VIEW_COUNT_CACHE.getExpireTime());
        return viewCount;
    }

    public Map<Long, Long> batchGetQuestionViewCount(List<Long> qidList) {
        if (CollectionUtils.isEmpty(qidList)) {
            return Collections.emptyMap();
        }
        Map<Long, Long> viewCountMap = new HashMap<>();
        List<String> fields = qidList.stream().map(String::valueOf).collect(Collectors.toList());
        String key = CTCRedisKeyEnum.CTC_QUESTION_VIEW_COUNT_CACHE.getKey();
        List<String> viewCountList = redisUtil.hmget(key, fields);
        for (int i = 0; i < qidList.size(); i++) {
            Long qid = qidList.get(i);
            String viewCountStr = viewCountList.get(i);
            Long viewCount = 0L;
            if (StringUtils.isNotBlank(viewCountStr)) {
                viewCount = Long.valueOf(viewCountStr);
            } else {
                viewCount = refreshQuestionViewCountCacheFromDB(qid);
            }
            viewCountMap.put(qid, viewCount);
        }
        return viewCountMap;
    }

    /**
     * 添加最新问题进入缓存
     *
     * @param pathId
     * @param questionId
     */
    private void addCCNewQuestionCache(Long pathId, Long questionId, Long time) {
        String key = CTCRedisKeyEnum.CTC_QUESTION_NEW_ZSET_CACHE.getKey(pathId.toString());
        if (!isExistKey(key)) {
            // 初始化社区最新问题缓存
            this.initCTCNewQuestionCache(pathId);
            return;
        }
        redisUtil.zadd(key, String.valueOf(questionId), time, CTCRedisKeyEnum.CTC_QUESTION_NEW_ZSET_CACHE.getExpireTime());
    }

    /**
     * 删除最新问题缓存中的指定栏目
     *
     * @param questionId
     */
    private void removeCCNewQuestionCache(Long pathId, Long questionId) {
        String key = CTCRedisKeyEnum.CTC_QUESTION_NEW_ZSET_CACHE.getKey(pathId.toString());
        if (!isExistKey(key)) {
            // 初始化社区最新栏目缓存
            this.initCTCNewQuestionCache(pathId);
            return;
        }
        redisUtil.zremove(key, String.valueOf(questionId));
    }

    private void initCTCNewQuestionCache(Long pathId) {
        String key = CTCRedisKeyEnum.CTC_QUESTION_NEW_ZSET_CACHE.getKey(pathId.toString());
        redisUtil.remove(key);
        List<CCQuestionEntity> questionEntities = questionDAO.findAllByPathIdAndStatusNotOrderByIdDesc(
                pathId, QuestionStatusEnum.DELETED.getStatus());
        if (CollectionUtils.isEmpty(questionEntities)) {
            return;
        }
        Set<ZSetOperations.TypedTuple<String>> tuples = questionEntities.stream().map(question -> {
            String value = String.valueOf(question.getId());
            Double score = Double.valueOf(question.getCreateDate().getTime());
            return new DefaultTypedTuple<>(value, score);
        }).collect(Collectors.toSet());
        redisUtil.zadd(key, tuples, CTCRedisKeyEnum.CTC_QUESTION_NEW_ZSET_CACHE.getExpireTime());
    }

    private void initCTCDoneQuestionCache(Long pathId) {
        String key = CTCRedisKeyEnum.CTC_QUESTION_DONE_ZSET_CACHE.getKey(pathId.toString());
        redisUtil.remove(key);
        List<CCQuestionEntity> questionEntities = questionDAO.findAllByPathIdAndStatusOrderByIdDesc(
                pathId, QuestionStatusEnum.DONE.getStatus());
        Set<ZSetOperations.TypedTuple<String>> tuples = questionEntities.stream().map(question -> {
            String value = String.valueOf(question.getId());
            Double score = Double.valueOf(question.getCreateDate().getTime());
            return new DefaultTypedTuple<>(value, score);
        }).collect(Collectors.toSet());
        redisUtil.zadd(key, tuples, CTCRedisKeyEnum.CTC_QUESTION_DONE_ZSET_CACHE.getExpireTime());
    }

    private boolean isExistKey(String key) {
        return redisUtil.exists(key);
    }

    /**
     * 查询问题的baseinfo
     * @param questionId
     * @return
     */
    public CTCQuestionBaseVO getQuestionBaseFromCache(Long questionId) {
        String key = CTCRedisKeyEnum.CTC_QUESTION_DETAIL_CACHE.getKey(questionId.toString());
        String cache = redisUtil.get(key);
        if (WebConstant.CACHE_NONE.equals(cache)) {
            return null;
        }
        if (StringUtils.isBlank(cache)) {
            return refreshQuestionDetailCache(questionId);
        }
        return JsonUtil.fromJson(cache, CTCQuestionBaseVO.class);
    }

    public List<CTCQuestionBaseVO> batchGetQuestionListFromCache(List<Long> questionIdSet) {
        if (CollectionUtils.isEmpty(questionIdSet)) {
            return Collections.emptyList();
        }
        List<CTCQuestionBaseVO> questionBaseVOList = new ArrayList<>(questionIdSet.size());
        List<String> keys = questionIdSet.stream().map(qid ->
                CTCRedisKeyEnum.CTC_QUESTION_DETAIL_CACHE.getKey(qid.toString())).collect(Collectors.toList());
        List<String> caches = redisUtil.mget(keys);
        for (int i = 0; i < questionIdSet.size(); i++) {
            Long qid = questionIdSet.get(i);
            String cache = caches.get(i);
            CTCQuestionBaseVO questionBase;
            if (StringUtils.isNotBlank(cache)) {
                questionBase = JsonUtil.fromJson(cache, CTCQuestionBaseVO.class);
            } else {
                questionBase = refreshQuestionDetailCache(qid);
            }
            if (questionBase != null) {
                questionBaseVOList.add(questionBase);
            }
        }
        return questionBaseVOList;
    }

    public Map<Long, CTCQuestionBaseVO> batchGetQuestionMapFromCache(List<Long> questionIdSet) {
        if (CollectionUtils.isEmpty(questionIdSet)) {
            return Collections.emptyMap();
        }
        Map<Long, CTCQuestionBaseVO> map = new HashMap<>(questionIdSet.size());
        List<String> keys = questionIdSet.stream().map(qid ->
                CTCRedisKeyEnum.CTC_QUESTION_DETAIL_CACHE.getKey(qid.toString())).collect(Collectors.toList());
        List<String> caches = redisUtil.mget(keys);
        for (int i = 0; i < questionIdSet.size(); i++) {
            Long qid = questionIdSet.get(i);
            String cache = caches.get(i);
            CTCQuestionBaseVO questionBase;
            if (StringUtils.isNotBlank(cache)) {
                questionBase = JsonUtil.fromJson(cache, CTCQuestionBaseVO.class);
            } else {
                questionBase = refreshQuestionDetailCache(qid);
            }
            if (questionBase != null) {
                map.put(qid, questionBase);
            }
        }
        return map;
    }

    private CTCQuestionBaseVO refreshQuestionDetailCache(Long questionId) {
        CCQuestionEntity question = questionDAO.findById(questionId).orElse(null);
        if (question == null) {
            return null;
        }
        return this.refreshQuestionDetailCache(question);
    }

    private CTCQuestionBaseVO refreshQuestionDetailCache(CCQuestionEntity question) {
        String key = CTCRedisKeyEnum.CTC_QUESTION_DETAIL_CACHE.getKey(question.getId().toString());
        if (question == null) {
            redisUtil.set(key, WebConstant.CACHE_NONE, CTCRedisKeyEnum.CTC_QUESTION_DETAIL_CACHE.getExpireTime());
            return null;
        }
        CTCQuestionBaseVO questionDetailVO = this.convertCTCQuestionDetailVO(question);
        redisUtil.set(key, JsonUtil.toJsonString(questionDetailVO), CTCRedisKeyEnum.CTC_QUESTION_DETAIL_CACHE.getExpireTime());
        return questionDetailVO;
    }

    private CTCQuestionBaseVO convertCTCQuestionDetailVO(CCQuestionEntity question) {
        CTCQuestionBaseVO questionDetailVO = new CTCQuestionBaseVO();
        BeanUtils.copyProperties(question, questionDetailVO);
        questionDetailVO.setImages(ListUtil.string2List(question.getImages()));
        questionDetailVO.setSigns(ListUtil.string2List(question.getSigns()));
        if (question.getTopDate() != null) {
            questionDetailVO.setTopTime(question.getTopDate().getTime());
        }
        if (question.getRecommendDate() != null) {
            questionDetailVO.setRecommendTime(question.getRecommendDate().getTime());
        }
        questionDetailVO.setCreateTime(question.getCreateDate().getTime());
        return questionDetailVO;
    }

    private CCQuestionEntity convertEntity(CCQuestionSaveRequestVO questionSaveRequest, String userId) {
        Long id = questionSaveRequest.getId();
        CCQuestionEntity questionEntity;
        Date now = new Date();
        if (id != null) {
            questionEntity = questionDAO.findById(id).orElse(null);
            Assert.isTrue(questionEntity != null, "更新失败，问题不存在");
            Assert.isTrue(questionEntity.getAuthor().equals(userId), "无权限！");
        } else {
            questionEntity = new CCQuestionEntity();
            questionEntity.setPathId(questionSaveRequest.getPathId());
            questionEntity.setAuthor(userId);
            questionEntity.setStatus(QuestionStatusEnum.NORMAL.getStatus());
            questionEntity.setCreateBy(userId);
            questionEntity.setCreateDate(now);
            questionEntity.setViewCount(0L);
            questionEntity.setFree(false);
        }
        questionEntity.setUpdateBy(userId);
        questionEntity.setUpdateDate(now);
        questionEntity.setType(questionSaveRequest.getType());
        questionEntity.setTitle(questionSaveRequest.getTitle());
        questionEntity.setDescription(questionSaveRequest.getDescription());
        questionEntity.setSigns(questionSaveRequest.getSigns());
        questionEntity.setRewardIntegral(null);
        if (QuestionTypeEnum.isReward(questionSaveRequest.getType())) {
            questionEntity.setRewardIntegral(questionSaveRequest.getRewardIntegral());
        }
        List<String> images = questionSaveRequest.getImages();
        questionEntity.setImages(null);
        if (CollectionUtils.isNotEmpty(images)) {
            questionEntity.setImages(Joiner.on(",").join(images));
        }
        return questionEntity;
    }

    @Override
    public Map<String, MessageResourceVO> batchGetMessResource(List<String> resourceIdList) {
        Map<String, MessageResourceVO> map = new HashMap<>();
        List<Long> ids = resourceIdList.stream().map(Long::valueOf).collect(Collectors.toList());
        Map<Long, CTCQuestionBaseVO> questionBaseVOMap = batchGetQuestionMapFromCache(ids);
        for (String resourceId : resourceIdList) {
            CTCQuestionBaseVO qa = questionBaseVOMap.get(Long.valueOf(resourceId));
            if (qa != null) {
                MessageResourceVO messageResource = new MessageResourceVO();
                messageResource.setId(resourceId);
                messageResource.setTitle(qa.getTitle());
                List<String> images = qa.getImages();
                if (CollectionUtils.isEmpty(images)) {
                    messageResource.setCover("http://coderutil.oss-cn-beijing.aliyuncs.com/bbs-image/file_724131b9690249efa7b9f34109856648.png");
                } else {
                    messageResource.setCover(images.get(0));
                }
                messageResource.setAuthor(qa.getAuthor());
                messageResource.setModule(ResourceTypeEnum.CTC_QUESTION.getTypeName());
                messageResource.setType(ResourceTypeEnum.CTC_QUESTION.getType());
                map.put(resourceId, messageResource);
            }
        }
        return map;
    }

    /**
     * 查询最新问题
     *
     * @param pathId
     * @param size
     * @return
     */
    public List<CTCHomeRecommendQuestionVO> getNewListFromCache(Long pathId, Integer size) {
        List<CTCHomeRecommendQuestionVO> list = scheduleService.getHomeQuestionFromCache(pathId);
        if (CollectionUtils.isEmpty(list)) {
            return Collections.emptyList();
        }
        ListUtil.shuffle(list);
        return list.subList(0, Math.min(size, list.size()));
    }
}
